// -*- mode:c++ -*-

// Copyright (c) 2009 The University of Edinburgh
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met: redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer;
// redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution;
// neither the name of the copyright holders nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Authors: Lei Zhang
// NOTE: Floating-point instructions are not implemented yet.

decode OPCODE_HI default Unknown::unknown() {

    0x0: decode OPCODE_LO {  
        // Generic integer format instructions.
        format IntOp {
            0x54: cmpsub({{ AFR = makeAFRFieldSub(Rs1_uw, Rs2_uw); }});
            0x5c: cmpadd({{ AFR = makeAFRFieldAdd(Rs1_uw, Rs2_uw); }});
            0x44: cmpand({{ uint32_t tmp = Rs1_uw & Rs2_uw;
                          AFR = makeAFRField(tmp); }});
            0x4c: cmpxor({{ uint32_t tmp = Rs1_uw ^ Rs2_uw;
                          AFR = makeAFRField(tmp); }});

            // double word compare
            0x56: dcmpsub({{ AFR = makeAFRFieldSub(Rs1, Rs2); }});
            0x5e: dcmpadd({{ AFR = makeAFRFieldAdd(Rs1, Rs2); }});
            0x46: dcmpand({{ uint64_t tmp = Rs1 & Rs2;
                          AFR = makeAFRField(tmp); }});
            0x4e: dcmpxor({{ uint64_t tmp = Rs1 ^ Rs2;
                          AFR = makeAFRField(tmp); }});
        }
    
        format IntImmOp {
    		// immediate
            0x55: cmpsubi({{ AFR = makeAFRFieldSub(Rs1_uw, (uint32_t)uimm); }});
            0x5d: cmpaddi({{ AFR = makeAFRFieldAdd(Rs1_uw, (uint32_t)uimm); }});
            0x45: cmpandi({{ uint32_t tmp = Rs1_uw & uimm;
                          AFR = makeAFRField(tmp); }});
            0x4d: cmpxori({{ uint32_t tmp = Rs1_uw ^ uimm;
                          AFR = makeAFRField(tmp); }});
            
            0x57: dcmpsubi({{ AFR = makeAFRFieldSub(Rs1, (uint64_t)uimm); }});
            0x5f: dcmpaddi({{ AFR = makeAFRFieldAdd(Rs1, (uint64_t)uimm); }});
            0x47: dcmpandi({{ uint64_t tmp = Rs1 & uimm;
                          AFR = makeAFRField(tmp); }});
            0x4f: dcmpxori({{ uint64_t tmp = Rs1 ^ uimm;
                          AFR = makeAFRField(tmp); }});
        }
    
        format IntArithOp {
            0x20: add({{ Rd_uw = Rs1_uw + Rs2_uw; }});
            0x24: adda({{ Rd_uw = Rs1_uw + Rs2_uw; }}, [updateAdd]);
            0x28: addc({{ Rd_uw = Rs1_uw + Rs2_uw + afr.ca; }});
            0x2c: addca({{ Rd_uw = Rs1_uw + Rs2_uw + afr.ca; }}, [updateAddc]);
            
            0x10: sub({{ Rd_uw = Rs1_uw - Rs2_uw; }});
            0x14: suba({{ Rd_uw = Rs1_uw - Rs2_uw; }}, [updateSub]);
            0x30: subc({{ Rd_uw = Rs1_uw + ~Rs2_uw + afr.ca; }});
            0x34: subca({{ Rd_uw = Rs1_uw + ~Rs2_uw + afr.ca; }}, [updateSubc]);
            
            0x18: rsub({{ Rd_uw = Rs2_uw - Rs1_uw; }});
            0x1c: rsuba({{ Rd_uw = Rs2_uw - Rs1_uw; }}, [updateRsub]);
            0x38: rsubc({{ Rd_uw = ~Rs1_uw + Rs2_uw + afr.ca; }});
            0x3c: rsubca({{ Rd_uw = ~Rs1_uw + Rs2_uw + afr.ca; }}, [updateRsubc]);
           
            // double word arithmetics 
            0x22: dadd({{ Rd = Rs1 + Rs2; }});
            0x26: dadda({{ Rd = Rs1 + Rs2; }}, [updateDadd]);
            0x2a: daddc({{ Rd = Rs1 + Rs2 + afr.ca; }});
            0x2e: daddca({{ Rd = Rs1 + Rs2 + afr.ca; }}, [updateDaddc]);
            
            0x12: dsub({{ Rd = Rs1 - Rs2; }});
            0x16: dsuba({{ Rd = Rs1 - Rs2; }}, [updateDsub]);
            0x32: dsubc({{ Rd = Rs1 + ~Rs2 + afr.ca; }});
            0x36: dsubca({{ Rd = Rs1 + ~Rs2 + afr.ca; }}, [updateDsubc]);

            0x1a: drsub({{ Rd = Rs2 - Rs1; }});
            0x1e: drsuba({{ Rd = Rs2 - Rs1; }}, [updateDrsub]);
            0x3a: drsubc({{ Rd = ~Rs1 + Rs2 + afr.ca; }});
            0x3e: drsubca({{ Rd = ~Rs1 + Rs2 + afr.ca; }}, [updateDrsubc]);
        }
     
        // NOTE!!!
        // All immediates in arithmetic instructions
        //   are unsigned-extended!!!
    	format IntImmArithOp {
            0x21: addi({{ Rd_uw = Rs1_uw + uimm; }});
            0x25: addia({{ Rd_uw = Rs1_uw + uimm; }}, [updateAdd]);
            0x29: addci({{ Rd_uw = Rs1_uw + uimm + afr.ca; }});
            0x2d: addcia({{ Rd_uw = Rs1_uw + uimm + afr.ca; }}, [updateAddc]);
            
            0x11: subi({{ Rd_uw = Rs1_uw - uimm; }});
            0x15: subia({{ Rd_uw = Rs1_uw - uimm; }}, [updateSub]);
            0x31: subci({{ Rd_uw = Rs1_uw + ~uimm + afr.ca; }});
            0x35: subcia({{ Rd_uw = Rs1_uw + ~uimm + afr.ca; }}, [updateSubc]);
            
            0x19: rsubi({{ Rd_uw = uimm - Rs1_uw; }});
            0x1d: rsubia({{ Rd_uw = uimm - Rs1_uw; }}, [updateRsub]);
            0x39: rsubci({{ Rd_uw = ~Rs1_uw + uimm + afr.ca; }});
            0x3d: rsubcia({{ Rd_uw = ~Rs1_uw + uimm + afr.ca; }}, [updateRsubc]);

            0x23: daddi({{ Rd = Rs1 + uimm; }});
            0x27: daddia({{ Rd = Rs1 + uimm; }}, [updateDadd]);
            0x2b: daddci({{ Rd = Rs1 + uimm + afr.ca; }});
            0x2f: daddcia({{ Rd = Rs1 + uimm + afr.ca; }}, [updateDaddc]);

            0x13: dsubi({{ Rd = Rs1 - uimm; }});
            0x17: dsubia({{ Rd = Rs1 - uimm; }}, [updateDsub]);
            0x33: dsubci({{ Rd = Rs1 + ~uimm + afr.ca; }});
            0x37: dsubcia({{ Rd = Rs1 + ~uimm + afr.ca; }}, [updateDsubc]);
            
            0x1b: drsubi({{ Rd = uimm - Rs1; }});
            0x1f: drsubia({{ Rd = uimm - Rs1; }}, [updateDrsub]);
            0x3b: drsubci({{ Rd = ~Rs1 + uimm + afr.ca; }});
            0x3f: drsubcia({{ Rd = ~Rs1 + uimm +  afr.ca; }}, [updateDrsubc]);
        }
    
    	// Integer logic instructions use source registers Rs1 and Rs2,
        // with destination register Rd.
        format IntLogicOp {
            0x0: and({{ Rd_uw = Rs1_uw & Rs2_uw; }});
            0x4: anda({{ Rd_uw = Rs1_uw & Rs2_uw; }}, [update]);
            0x8: xor({{ Rd_uw = Rs1_uw ^ Rs2_uw; }});
            0xc: xora({{ Rd_uw = Rs1_uw ^ Rs2_uw; }}, [update]);
            0x60: or({{ Rd_uw = Rs1_uw | Rs2_uw; }});
            0x64: ora({{ Rd_uw = Rs1_uw | Rs2_uw; }}, [update]);
            0x68: mov({{ Rd_uw = Rs2_uw; }});
            0x70: andn({{ Rd_uw = Rs1_uw & ~Rs2_uw; }});
            0x74: andna({{ Rd_uw = Rs1_uw & ~Rs2_uw; }}, [update]);
            0x78: not({{ Rd_uw = ~Rs2_uw; }});
            0x80: lsl({{
                if (Rs2_uw >= 32) {
                    Rd_uw = 0;
                } 
                else {
                    Rd_uw = Rs1_uw << (Rs2_uw & 0x1f);
                }
            }});
            0x88: lsr({{
                if (Rs2_uw >= 32) {
                    Rd_uw = 0;
                } 
                else  {
                    Rd_uw = Rs1_uw >> (Rs2_uw & 0x1f);
                }
            }});
            0x90: asr({{
                int32_t s = Rs1_uw;
                uint32_t tmp;
                if (Rs2_uw == 0) {
                    tmp = Rs1_uw;
                } 
                else if (Rs2_uw >= 32) {
                    if (s < 0) {
                        tmp = (uint32_t)-1;
                    } 
                    else {
                        tmp = 0;
                    }
                } 
                else {
                    tmp = s >> (Rs2_uw & 0x1f);
                }
                Rd_uw = tmp;
            }});
            0xc4: cntlz({{ Rd_uw = Rs1_uw == 0 ? 32 : 31 - findMsbSet(Rs1_uw); }});
            0xc0: cntlo({{ uint32_t src = ~Rs1_uw;
                          Rd_uw = src == 0 ? 32 : 31 - findMsbSet(src); }});
            // double word arithmetics
            0x2: dand({{ Rd = Rs1 & Rs2; }});
            0x6: danda({{ Rd = Rs1 & Rs2; }}, [updateD]);
            0xa: dxor({{ Rd = Rs1 ^ Rs2; }});
            0xe: dxora({{ Rd = Rs1 ^ Rs2; }}, [updateD]);
            0x62: dor({{ Rd = Rs1 | Rs2; }});
            0x66: dora({{ Rd = Rs1 | Rs2; }}, [updateD]);
            0x72: dandn({{ Rd = Rs1 & ~Rs2; }});
            0x76: dandna({{ Rd = Rs1 & ~Rs2; }}, [updateD]);
            0x7a: dnot({{ Rd = ~Rs2; }});
            0x6a: dmov({{ Rd = Rs2; }});
            0x82: dlsl({{
                if (Rs2 >= 64) {
                    Rd = 0;
                } 
                else {
                    Rd = Rs1 << (Rs2 & 0x3f);
                }
            }});
            0x8a: dlsr({{
                if (Rs2 >= 64) {
                    Rd = 0;
                } 
                else  {
                    Rd = Rs1 >> (Rs2 & 0x3f);
                }
            }});
            0x92: dasr({{
                int64_t s = Rs1;
                if (Rs2 == 0) {
                    Rd = Rs1;
                } 
                else if (Rs2 >= 64) {
                    if (s < 0) {
                        Rd = (uint64_t)-1;
                    } 
                    else {
                        Rd = 0;
                    }
                } 
                else {
                    Rd = s >> (Rs2 & 0x3f);
                }
            }});
            0xc6: dcntlz({{ Rd = Rs1 == 0 ? 64 : 63 - findMsbSet(Rs1); }});
            0xc2: dcntlo({{ uint64_t src = ~Rs1;
                          Rd = src == 0 ? 64 : 63 - findMsbSet(src); }});
        }
       
        format IntImmLogicOp {
            0x61: ori({{ Rd_uw = Rs1_uw | uimm; }});
            0x65: oria({{ Rd_uw = Rs1_uw | uimm; }}, [update]);
            0x9: xori({{ Rd_uw = Rs1_uw ^ uimm; }});
            0xd: xoria({{ Rd_uw = Rs1_uw ^ uimm; }}, [update]);
            0x1: andi({{ Rd_uw = Rs1_uw & uimm; }});
            0x5: andia({{ Rd_uw = Rs1_uw & uimm; }}, [update]);
            0x71: andni({{ Rd_uw = Rs1_uw & ~uimm; }});
            0x75: andnia({{ Rd_uw = Rs1_uw & ~uimm; }}, [update]);
            0x69: movi({{ Rd_uw = uimm; }});
            0x79: noti({{ Rd_uw = ~uimm; }});
            0x81: lsli({{
                if (uimm >= 32) {
                    Rd_uw = 0;
                } 
                else {
                    Rd_uw = Rs1_uw << (uimm & 0x1f);
                }
            }});
            0x89: lsri({{
                if (uimm >= 32) {
                    Rd_uw = 0;
                } 
                else  {
                    Rd_uw = Rs1_uw >> (uimm & 0x1f);
                }
            }});
            0x91: asri({{
                int32_t s = Rs1_uw;
                uint32_t tmp;
                if (uimm == 0) {
                    tmp = Rs1_uw;
                } 
                else if (uimm >= 32) {
                    if (s < 0) {
                        tmp = (uint32_t)-1;
                    } 
                    else {
                        tmp = 0;
                    }
                } 
                else {
                    tmp = s >> (uimm & 0x1f);
                }
                Rd_uw = tmp
            }});

            // double word logics
            0x63: dori({{ Rd = Rs1 | uimm; }});
            0x67: doria({{ Rd = Rs1 | uimm; }}, [updateD]);
            0xb: dxori({{ Rd = Rs1 ^ uimm; }});
            0xf: dxoria({{ Rd = Rs1 ^ uimm; }}, [updateD]);
            0x3: dandi({{ Rd = Rs1 & uimm; }});
            0x7: dandia({{ Rd = Rs1 & uimm; }}, [updateD]);
            0x73: dandni({{ Rd = Rs1 & ~uimm; }});
            0x77: dandnia({{ Rd = Rs1 & ~uimm; }}, [updateD]);
            0x6b: dmovi({{ Rd = uimm; }});
            0x7b: dnoti({{ Rd = ~uimm; }});
            0x83: dlsli({{
                if (uimm >= 64) {
                    Rd = 0;
                } 
                else {
                    Rd = Rs1 << (uimm & 0x3f);
                }
            }});
            0x8b: dlsri({{
                if (uimm >= 64) {
                    Rd = 0;
                } 
                else  {
                    Rd = Rs1 >> (uimm & 0x3f);
                }
            }});
            0x93: dasri({{
                int64_t s = Rs1;
                if (uimm == 0) {
                    Rd = Rs1;
                } 
                else if (uimm >= 64) {
                    if (s < 0) {
                        Rd = (uint64_t)-1;
                    } 
                    else {
                        Rd = 0;
                    }
                } 
                else {
                    Rd = s >> (uimm & 0x3f);
                }
            }});
        }
        
        format IntLogicOpCond {
            0xe8: cmovcc({{ Rd = Rs2_uw; }});
            0xf8: cnotcc({{ Rd = ~Rs2_uw; }});
            0xea: dcmovcc({{ Rd = Rs2; }});
            0xfa: dcnotcc({{ Rd = ~Rs2; }});
        }

        format IntImmLogicOpCond {
            0xe9: cmovcci({{ Rd = uimm & 0xffffffff; }});
            0xf9: cnotcci({{ Rd = ~uimm & 0xffffffff; }});
            0xeb: dcmovcci({{ Rd = uimm; }});
            0xfb: dcnotcci({{ Rd = ~uimm; }});
        }
    }

    // load/store instructions
    0x3: decode OPCODE_LO {

        // index, i.e., register + register
        format LoadIndexOp {
            // pre-add (default)
            0xd8: ldbx({{ Rd = Mem_ub; }});
            0xda: ldsbx({{ Rd = Mem_sb; }});
            0xd4: ldhx({{ Rd = Mem_uh; }});
            0xd6: ldshx({{ Rd = Mem_sh; }});
            0xdc: ldwx({{ Rd = Mem_uw; }});
            0xde: ldswx({{ Rd = Mem_sw; }});
            0xd2: lddx({{ Rd = Mem; }});
           
            // pre-sub 
            0x58: ldbxs({{ Rd = Mem_ub; }}, {{ EA = Rs1 - Rs2; }});
            0x5a: ldsbxs({{ Rd = Mem_sb; }}, {{ EA = Rs1 - Rs2; }});
            0x54: ldhxs({{ Rd = Mem_uh; }}, {{ EA = Rs1 - Rs2; }});
            0x56: ldshxs({{ Rd = Mem_sh; }}, {{ EA = Rs1 - Rs2; }});
            0x5c: ldwxs({{ Rd = Mem_uw; }}, {{ EA = Rs1 - Rs2; }});
            0x5e: ldswxs({{ Rd = Mem_sw; }}, {{ EA = Rs1 - Rs2; }});
            0x52: lddxs({{ Rd = Mem; }}, {{ EA = Rs1 - Rs2; }});
        }
    
        // write back
        format LoadIndexUpdateOp {
            // pre-add
            0xf8: ldbxw({{ Rd = Mem_ub; }});
            0xfa: ldsbxw({{ Rd = Mem_sb; }});
            0xf4: ldhxw({{ Rd = Mem_uh; }});
            0xf6: ldshxw({{ Rd = Mem_sh; }});
            0xfc: ldwxw({{ Rd = Mem_uw; }});
            0xfe: ldswxw({{ Rd = Mem_sw; }});
            0xf2: lddxw({{ Rd = Mem; }});
            
            // pre-sub
            0x78: ldbxsw({{ Rd = Mem_ub; }}, {{ EA = Rs1 - Rs2; }}, 
                           is_sub = true);
            0x7a: ldsbxsw({{ Rd = Mem_sb; }}, {{ EA = Rs1 - Rs2; }}, 
                           is_sub = true);
            0x74: ldhxsw({{ Rd = Mem_uh; }}, {{ EA = Rs1 - Rs2; }},
                           is_sub = true);
            0x76: ldshxsw({{ Rd = Mem_sh; }}, {{ EA = Rs1 - Rs2; }},
                           is_sub = true);
            0x7c: ldwxsw({{ Rd = Mem_uw; }}, {{ EA = Rs1 - Rs2; }},
                           is_sub = true);
            0x7e: ldswxsw({{ Rd = Mem_sw; }}, {{ EA = Rs1 - Rs2; }},
                           is_sub = true);
            0x72: lddxsw({{ Rd = Mem; }}, {{ EA = Rs1 - Rs2; }},
                           is_sub = true);

            // post-add
            0xb8: ldbxpw({{ Rd = Mem_ub; }}, {{ EA = Rs1; }});
            0xba: ldsbxpw({{ Rd = Mem_sb; }}, {{ EA = Rs1; }});
            0xb4: ldhxpw({{ Rd = Mem_uh; }}, {{ EA = Rs1; }});
            0xb6: ldshxpw({{ Rd = Mem_sh; }}, {{ EA = Rs1; }});
            0xbc: ldwxpw({{ Rd = Mem_uw; }}, {{ EA = Rs1; }});
            0xbe: ldswxpw({{ Rd = Mem_sw; }}, {{ EA = Rs1; }});
            0xb2: lddxpw({{ Rd = Mem; }}, {{ EA = Rs1; }});

            // post-sub
            0x38: ldbxpsw({{ Rd = Mem_ub; }}, {{ EA = Rs1; }}, is_sub = true);
            0x3a: ldsbxpsw({{ Rd = Mem_sb; }}, {{ EA = Rs1; }}, is_sub = true);
            0x34: ldhxpsw({{ Rd = Mem_uh; }}, {{ EA = Rs1; }}, is_sub = true);
            0x36: ldshxpsw({{ Rd = Mem_sh; }}, {{ EA = Rs1; }}, is_sub = true);
            0x3c: ldwxpsw({{ Rd = Mem_uw; }}, {{ EA = Rs1; }}, is_sub = true);
            0x3e: ldswxpsw({{ Rd = Mem_sw; }}, {{ EA = Rs1; }}, is_sub = true);
            0x32: lddxpsw({{ Rd = Mem; }}, {{ EA = Rs1; }}, is_sub = true);
        }
    
    	// displace, i.e., immediate + register
        format LoadDispOp {
            // pre-add
            0xd9: ldb({{ Rd = Mem_ub; }});
            0xdb: ldsb({{ Rd = Mem_sb; }});
            0xd5: ldh({{ Rd = Mem_uh; }});
            0xd7: ldsh({{ Rd = Mem_sh; }});
            0xdd: ldw({{ Rd = Mem_uw; }});
            0xdf: ldsw({{ Rd = Mem_sw; }});
            0xd3: ldd({{ Rd = Mem; }});
            
            // pre-sub
            0x59: ldbs({{ Rd = Mem_ub; }}, {{ EA = Rs1 - disp; }});
            0x5b: ldsbs({{ Rd = Mem_sb; }}, {{ EA = Rs1 - disp; }});
            0x55: ldhs({{ Rd = Mem_uh; }}, {{ EA = Rs1 - disp; }});
            0x57: ldshs({{ Rd = Mem_sh; }}, {{ EA = Rs1 - disp; }});
            0x5d: ldws({{ Rd = Mem_uw; }}, {{ EA = Rs1 - disp; }});
            0x5f: ldsws({{ Rd = Mem_sw; }}, {{ EA = Rs1 - disp; }});
            0x53: ldds({{ Rd = Mem; }}, {{ EA = Rs1 - disp; }});
        }
    
        format LoadDispUpdateOp {
            // pre-add
            0xf9: ldbw({{ Rd = Mem_ub; }});
            0xfb: ldsbw({{ Rd = Mem_sb; }});
            0xf5: ldhw({{ Rd = Mem_uh; }});
            0xf7: ldshw({{ Rd = Mem_sh; }});
            0xfd: ldww({{ Rd = Mem_uw; }});
            0xff: ldsww({{ Rd = Mem_sw; }});
            0xf3: lddw({{ Rd = Mem; }});
            
            // pre-sub
            0x79: ldbsw({{ Rd = Mem_ub; }}, {{ EA = Rs1 - disp; }}, 
                           is_sub = true);
            0x7b: ldsbsw({{ Rd = Mem_sb; }}, {{ EA = Rs1 - disp; }}, 
                           is_sub = true);
            0x75: ldhsw({{ Rd = Mem_uh; }}, {{ EA = Rs1 - disp; }},
                           is_sub = true);
            0x77: ldshsw({{ Rd = Mem_sh; }}, {{ EA = Rs1 - disp; }},
                           is_sub = true);
            0x7d: ldwsw({{ Rd = Mem_uw; }}, {{ EA = Rs1 - disp; }},
                           is_sub = true);
            0x7f: ldswsw({{ Rd = Mem_sw; }}, {{ EA = Rs1 - disp; }},
                           is_sub = true);
            0x73: lddsw({{ Rd = Mem; }}, {{ EA = Rs1 - disp; }},
                           is_sub = true);

            // post-add
            0xb9: ldbpw({{ Rd = Mem_ub; }}, {{ EA = Rs1; }});
            0xbb: ldsbpw({{ Rd = Mem_sb; }}, {{ EA = Rs1; }});
            0xb5: ldhpw({{ Rd = Mem_uh; }}, {{ EA = Rs1; }});
            0xb7: ldshpw({{ Rd = Mem_sh; }}, {{ EA = Rs1; }});
            0xbd: ldwpw({{ Rd = Mem_uw; }}, {{ EA = Rs1; }});
            0xbf: ldswpw({{ Rd = Mem_sw; }}, {{ EA = Rs1; }});
            0xb3: lddpw({{ Rd = Mem; }}, {{ EA = Rs1; }});
            
            // post-sub
            0x39: ldbpsw({{ Rd = Mem_ub; }}, {{ EA = Rs1; }}, is_sub = true);
            0x3b: ldsbpsw({{ Rd = Mem_sb; }}, {{ EA = Rs1; }}, is_sub = true);
            0x35: ldhpsw({{ Rd = Mem_uh; }}, {{ EA = Rs1; }}, is_sub = true);
            0x37: ldshpsw({{ Rd = Mem_sh; }}, {{ EA = Rs1; }}, is_sub = true);
            0x3d: ldwpsw({{ Rd = Mem_uw; }}, {{ EA = Rs1; }}, is_sub = true);
            0x3f: ldswpsw({{ Rd = Mem_sw; }}, {{ EA = Rs1; }}, is_sub = true);
            0x33: lddpsw({{ Rd = Mem; }}, {{ EA = Rs1; }}, is_sub = true);
        }
    
        format StoreIndexOp {
            // pre-add
            0xc8: stbx({{ Mem_ub = Rd_ub; }});
            0xc4: sthx({{ Mem_uh = Rd_uh; }});
            0xcc: stwx({{ Mem_uw = Rd_uw; }});
            0xc2: stdx({{ Mem = Rd; }});
            // pre-sub
            0x48: stbxs({{ Mem_ub = Rd_ub; }}, {{ EA = Rs1 - Rs2; }});
            0x44: sthxs({{ Mem_uh = Rd_uh; }}, {{ EA = Rs1 - Rs2; }});
            0x4c: stwxs({{ Mem_uw = Rd_uw; }}, {{ EA = Rs1 - Rs2; }});
            0x42: stdxs({{ Mem = Rd; }}, {{ EA = Rs1 - Rs2; }});
        }
    
        format StoreIndexUpdateOp {
            // pre-add
            0xe8: stbxw({{ Mem_ub = Rd_ub; }});
            0xe4: sthxw({{ Mem_uh = Rd_uh; }});
            0xec: stwxw({{ Mem_uw = Rd_uw; }});
            0xe2: stdxw({{ Mem = Rd; }});
            // pre-sub
            0x68: stbxsw({{ Mem_ub = Rd_ub; }}, {{ EA = Rs1 - Rs2; }},
                           is_sub = true);
            0x64: sthxsw({{ Mem_uh = Rd_uh; }}, {{ EA = Rs1 - Rs2; }},
                           is_sub = true);
            0x6c: stwxsw({{ Mem_uw = Rd_uw; }}, {{ EA = Rs1 - Rs2; }},
                           is_sub = true);
            0x62: stdxsw({{ Mem = Rd; }}, {{ EA = Rs1 - Rs2; }},
                           is_sub = true);
            // post-add
            0xa8: stbxpw({{ Mem_ub = Rd_ub; }}, {{ EA = Rs1; }});
            0xa4: sthxpw({{ Mem_uh = Rd_uh; }}, {{ EA = Rs1; }});
            0xac: stwxpw({{ Mem_uw = Rd_uw; }}, {{ EA = Rs1; }});
            0xa2: stdxpw({{ Mem = Rd; }}, {{ EA = Rs1; }});
            // post-sub
            0x28: stbxpsw({{ Mem_ub = Rd_ub; }}, {{ EA = Rs1; }}, 
                           is_sub = true);
            0x24: sthxpsw({{ Mem_uh = Rd_uh; }}, {{ EA = Rs1; }}, 
                           is_sub = true);
            0x2c: stwxpsw({{ Mem_uw = Rd_uw; }}, {{ EA = Rs1; }}, 
                           is_sub = true);
            0x22: stdxpsw({{ Mem = Rd; }}, {{ EA = Rs1; }}, 
                           is_sub = true);
        }
    
        format StoreDispOp {
            // pre-add
            0xc9: stb({{ Mem_ub = Rd_ub; }});
            0xc5: sth({{ Mem_uh = Rd_uh; }});
            0xcd: stw({{ Mem_uw = Rd_uw; }});
            0xc3: std({{ Mem = Rd; }});
            // pre-sub
            0x49: stbs({{ Mem_ub = Rd_ub; }}, {{ EA = Rs1 - disp; }});
            0x45: sths({{ Mem_uh = Rd_uh; }}, {{ EA = Rs1 - disp; }});
            0x4d: stws({{ Mem_uw = Rd_uw; }}, {{ EA = Rs1 - disp; }});
            0x43: stds({{ Mem = Rd; }}, {{ EA = Rs1 - disp; }});
        }
    
        format StoreDispUpdateOp {
            // pre-add
            0xe9: stbw({{ Mem_ub = Rd_ub; }});
            0xe5: sthw({{ Mem_uh = Rd_uh; }});
            0xed: stww({{ Mem_uw = Rd_uw; }});
            0xe3: stdw({{ Mem = Rd; }});
            // pre-sub
            0x69: stbsw({{ Mem_ub = Rd_ub; }}, {{ EA = Rs1 - disp; }},
                           is_sub = true);
            0x65: sthsw({{ Mem_uh = Rd_uh; }}, {{ EA = Rs1 - disp; }},
                           is_sub = true);
            0x6d: stwsw({{ Mem_uw = Rd_uw; }}, {{ EA = Rs1 - disp; }},
                           is_sub = true);
            0x63: stdsw({{ Mem = Rd; }}, {{ EA = Rs1 - disp; }},
                           is_sub = true);
            // post-add
            0xa9: stbpw({{ Mem_ub = Rd_ub; }}, {{ EA = Rs1; }});
            0xa5: sthpw({{ Mem_uh = Rd_uh; }}, {{ EA = Rs1; }});
            0xad: stwpw({{ Mem_uw = Rd_uw; }}, {{ EA = Rs1; }});
            0xa3: stdpw({{ Mem = Rd; }}, {{ EA = Rs1; }});

            // post-sub
            0x29: stbpsw({{ Mem_ub = Rd_ub; }}, {{ EA = Rs1; }}, is_sub = true);
            0x25: sthpsw({{ Mem_uh = Rd_uh; }}, {{ EA = Rs1; }}, is_sub = true);
            0x2d: stwpsw({{ Mem_uw = Rd_uw; }}, {{ EA = Rs1; }}, is_sub = true);
            0x23: stdpsw({{ Mem = Rd; }}, {{ EA = Rs1; }}, is_sub = true);
        }
    }

    0x2: decode OPCODE_LO {
        // Arithmetic instructions all use source registers Ra and Rb,
        // with destination register Rt.
        format IntArithOp {
            0x0: mul({{ int64_t prod = Rs1_sw * Rs2_sw; Rd_uw = prod; }});
            0x4: mula({{ int64_t prod = Rs1_sw * Rs2_sw; Rd_uw = prod; }}, [update]);
            
            // mul and add(accumulate)
            // FIXME: 'mula' cannot be used here since it's used by mul.a
            0x40: mulc({{ int64_t prod = Rs1_sw * Rs2_sw; 
                          Rd_uw = Rd_uw + prod; }});
            0x44: mulca({{ int64_t prod = Rs1_sw * Rs2_sw; 
                          Rd_uw = Rd_uw + prod; }}, [update]);
    		
            // signed
            0x80: divs({{
                int32_t src1 = Rs1_sw;
                int32_t src2 = Rs2_sw;
                if ((src1 != 0x80000000 || src2 != 0xffffffff)
                    && src2 != 0) {
                    Rd_uw = src1 / src2;
                } 
                else {
                    Rd_uw = 0;
                }
            }});
                
            0x84: divsa({{
                int32_t src1 = Rs1_sw;
                int32_t src2 = Rs2_sw;
                if ((src1 != 0x80000000 || src2 != 0xffffffff)
                    && src2 != 0) {
                    Rd_uw = src1 / src2;
                } 
                else {
                    Rd_uw = 0;
                }
            }},
            [update]);
               
    		// unsigned 
            0xc0: divu({{
                uint32_t src1 = Rs1_uw;
                uint32_t src2 = Rs2_uw;
                if (src2 != 0) {
                    Rd_uw = src1 / src2;
                } 
                else {
                    Rd_uw = 0;
                }
            }});
            0xc4: divua({{
              uint32_t src1 = Rs1_uw;
              uint32_t src2 = Rs2_uw;
              if (src2 != 0) {
                  Rd_uw = src1 / src2;
              } 
              else {
                  Rd_uw = 0;
              }
            }},
            [update]);
           
            // double word arithmetics
            0x2: dmul({{ int64_t prod = Rs1 * Rs2; Rd = prod; }});
            0x6: dmula({{ int64_t prod = Rs1 * Rs2; Rd = prod; }}, [updateD]);
            0x42: dmulc({{ int64_t prod = Rs1 * Rs2; Rd = Rd + prod; }});
            0x46: dmulca({{ int64_t prod = Rs1 * Rs2; Rd = Rd + prod; }}, [updateD]);
            
            // signed
            0x82: ddivs({{
                int64_t src1 = Rs1;
                int64_t src2 = Rs2;
                if ((src1 != 0x8000000000000000 || src2 != 0xffffffffffffffff)
                    && src2 != 0) {
                    Rd = src1 / src2;
                } 
                else {
                    Rd = 0;
                }
            }});
                
            0x86: ddivsa({{
                int64_t src1 = Rs1;
                int64_t src2 = Rs2;
                if ((src1 != 0x8000000000000000 || src2 != 0xffffffffffffffff)
                    && src2 != 0) {
                    Rd = src1 / src2;
                } 
                else {
                    Rd = 0;
                }
            }},
            [updateD]);
               
    		// unsigned 
            0xc2: ddivu({{
                uint64_t src1 = Rs1;
                uint64_t src2 = Rs2;
                if (src2 != 0) {
                    Rd = src1 / src2;
                } 
                else {
                    Rd = 0;
                }
            }});
            0xc6: ddivua({{
              uint64_t src1 = Rs1;
              uint64_t src2 = Rs2;
              if (src2 != 0) {
                  Rd = src1 / src2;
              } 
              else {
                  Rd = 0;
              }
            }},
            [updateD]);

        }
    }

    0x5: decode BC_HI {
        // Unconditionally branch to fixed address.
        0xf: decode BC_LO {
            0x0: decode LK {
                format BranchNonPCRel {
                    0x0: jump({{ NPC = Rs1; }});
                    0x1: callr({{ NPC = Rs1; }}, [IsCall]);
                }
            } 
            0x4: BranchNonPCRel::return({{ NPC = LR; }});
            //0x6: eret
        }
        
        0xe: decode LK {
            // FIXME: bal should be a variation of bcc
            0x0: BranchPCRel::bal({{ NPC = (uint64_t)(PC + disp); }});
            0x1: BranchPCRel::call({{ NPC = (uint64_t)(PC + disp); }}, 
                                   [IsCall]);
        }
        
        default: decode LK {
            // Conditionally branch relative to PC based on AFR.
            format BranchPCRelCond {
                0x0: bcc({{ NPC = (uint64_t)(PC + disp); }});
                0x1: bccl({{ NPC = (uint64_t)(PC + disp); }}); // with link
            }
        }
        // FIXME: other control-flow instructions
    }
    
    0x7: decode LK {
        0x1: decode BC_HI {
            // FIXME: disp = imm << 2 (in the branch instruction) 
            // we should shift disp back to get imm, the syscall number
            0x0: BranchPCRel::jepriv({{ xc->syscall(disp >> 2); }},
                         [IsSyscall, IsNonSpeculative, IsSerializeAfter]);
        }
        0x0: BranchPCRelCond::jeprivcc({{ xc->syscall(disp >> 2); }},
                     [IsSyscall, IsNonSpeculative, IsSerializeAfter]);
    }
    
    // stone: only to output the content of a register
    0x6: IntImmOp::movc({{ 
                 uint32_t num = uimm >> 2;
                 switch (num) {
                   case 0:
                     printf("stone's output: %ld\n", Rs1);
                     break;
                   case 1:
                     printf("_nick's output: %ld\n", Rs1); 
                     break;
                   default:
                     break;
                 }
                 }});

    0x1: decode OPCODE_LO {
        format IntArithOp {
            0x60: readafr({{ Rd = AFR; }});
            0xe0: setafr({{ AFR = Rs2 & 0xf; }}); 
        }
        0xe1: IntImmArithOp:: setafri({{ AFR = uimm & 0xf; }});        
    }
}

